package com.google.code.swingonfire.component.page;

import java.awt.Component;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.LayoutManager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.swing.JComponent;

import com.google.code.swingonfire.animation.Animation;
import com.google.code.swingonfire.animation.AnimationFinishedConsumer;
import com.google.code.swingonfire.animation.AnimationStartingConsumer;
import com.google.code.swingonfire.animation.Animator;
import com.google.code.swingonfire.image.ComponentBufferImageProvider;
import com.google.code.swingonfire.image.ImageProvider;
import com.google.code.swingonfire.utility.Utils;
import com.google.code.swingonfire.utility.interpolation.Interpolation;
import com.google.code.swingonfire.utility.interpolation.Interpolations;

/**
 * Abstract implementation of a component that holds another one (full viewport) which can be easily switched. The
 * transition between components is animated.
 * 
 * @author thred
 */
public abstract class AbstractJPageComponent extends JComponent implements Animation, AnimationStartingConsumer,
    AnimationFinishedConsumer
{

    private static final long serialVersionUID = 8975293268308546683L;
    private static final int DEFAULT_COMPONENT_CACHE_SIZE = 8;

    protected static class Entry
    {
        private final Component component;
        private final ImageProvider imageProvider;

        public Entry(Component component)
        {
            super();

            this.component = component;

            imageProvider = new ComponentBufferImageProvider(component, true, 4);
        }

        public Object getName()
        {
            return (component != null) ? component.getName() : null;
        }

        public Component getComponent()
        {
            return component;
        }

        public ImageProvider getImageProvider()
        {
            return imageProvider;
        }

        public void activate()
        {
            if (component != null)
            {
                component.setVisible(true);
            }
        }

        public void inactivate()
        {
            if (component != null)
            {
                component.setVisible(false);
            }
        }

    }

    private final Map<Integer, Entry> entries = new LinkedHashMap<Integer, Entry>(DEFAULT_COMPONENT_CACHE_SIZE + 1,
        1.1f, true);

    private JPagePaneComponentProvider componentProvider;
    private int componentCacheSize = DEFAULT_COMPONENT_CACHE_SIZE;

    private int minimumComponentIndex = 0;
    private int maximumComponentIndex = -1;

    private boolean animationEnabled = true;
    private double animationDuration = 0.5;
    private JPageComponentAnimation animation = JPageComponentAnimations.horizontal();
    private Interpolation interpolation = Interpolations.accurate();

    private boolean repaintEarlyEnabled = true;

    private Integer selectedComponentIndex = null;

    private Entry activeEntry = null;

    private long animationStartMillis;
    private long animationEndMillis;
    private double animationStartIndex;
    private double animationEndIndex;
    private boolean animationRunning = false;

    private double displayIndex;
    private int displayOutgoingIndex;
    private int displayIncomingIndex;
    private boolean displayStill = false;
    private boolean displayForward = false;
    private double displayFraction;

    public AbstractJPageComponent()
    {
        super();

        super.setLayout(JPageComponentLayout.INSTANCE);
    }

    /**
     * Adds a component at the maximumComponentIndex+1 and increments the maximumComponentIndex. {@inheritDoc}
     */
    @Override
    public Component add(Component component)
    {
        addComponent(component, null, maximumComponentIndex + 1);

        maximumComponentIndex += 1;

        return component;
    }

    /**
     * Adds a component at the maximumComponentIndex+1 and increments the maximumComponentIndex. {@inheritDoc}
     */
    @Override
    public Component add(String name, Component component)
    {
        addComponent(component, name, maximumComponentIndex + 1);

        maximumComponentIndex += 1;

        return component;
    }

    /**
     * Adds a component at the specified index. {@inheritDoc}
     */
    @Override
    public Component add(Component component, int index)
    {
        addComponent(component, null, index);

        return component;
    }

    /**
     * Adds a component at the maximumComponentIndex+1 and increments the maximumComponentIndex. {@inheritDoc}
     */
    @Override
    public void add(Component component, Object constraints)
    {
        addComponent(component, constraints, maximumComponentIndex + 1);

        maximumComponentIndex += 1;
    }

    /**
     * Adds a component at the specified index. {@inheritDoc}
     */
    @Override
    public void add(Component component, Object constraints, int index)
    {
        addComponent(component, constraints, index);
    }

    /**
     * Adds a component
     * 
     * @param component the component
     * @param constraints the constraints (ignored)
     * @param index the index
     */
    protected void addComponent(Component component, Object constraints, int index)
    {
        addEntry(component, index);

        if (selectedComponentIndex == null)
        {
            setSelectedComponentIndex(index, false);
        }
    }

    /**
     * Adds an component (and it's entry).
     * 
     * @param component the component
     * @param index the index
     * @return the entry
     */
    protected Entry addEntry(Component component, int index)
    {
        Entry entry = new Entry(component);

        entry.inactivate();
        entries.put(index, entry);

        JPageComponentLayout.INSTANCE.layoutComponent(this, component);

        super.add(component);

        invalidate();
        cleanupComponentCache();

        return entry;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getComponentCount()
    {
        return super.getComponentCount();
    }

    /**
     * Returns the component at the specified index, if it was initialized. The component provider will not get asked
     * for the component. {@inheritDoc}
     */
    @Override
    public Component getComponent(int index)
    {
        return super.getComponent(index);
    }

    /**
     * Returns the component at the specified index. If initialize is true, a component provider will be asked for the
     * component, if it was not yet initialized.
     * 
     * @param index the index
     * @param initialize true to initialize
     * @return the component, or null
     */
    protected Component getComponent(int index, boolean initialize)
    {
        Entry entry = getEntry(index, initialize);

        return (entry != null) ? entry.getComponent() : null;
    }

    /**
     * Returns the image provider at the specified index. If initialize is true, a component provider will be asked for
     * the component, if it was not yet initialized.
     * 
     * @param index the index
     * @param initialize true to initialize
     * @return the image provider, or null
     */
    protected ImageProvider getImageProvider(int index, boolean initialize)
    {
        Entry entry = getEntry(index, initialize);

        return (entry != null) ? entry.getImageProvider() : null;
    }

    /**
     * Updates the image providers in the specified range. The start index may be >= endIndex (reverse update possible).
     * 
     * @param startIndex the start index
     * @param endIndex the end index
     * @param timeout if > 0, the update will be stopped after the specified amount of milliseconds
     */
    protected void updateImageProviders(int startIndex, int endIndex, long timeout)
    {
        long millis = System.currentTimeMillis();
        int increment = (startIndex < endIndex) ? 1 : -1;

        for (int i = startIndex; i != endIndex; i += increment)
        {
            Entry entry = entries.get(i);

            if (entry != null)
            {
                entry.getImageProvider().getImage();
            }

            if ((timeout > 0) && ((System.currentTimeMillis() - millis) > timeout))
            {
                break;
            }
        }
    }

    /**
     * Returns the component at the specified index. If initialize is true, a component provider will be asked for the
     * component, if it was not yet initialized.
     * 
     * @param index the index
     * @param initialize true to initialize (only possible if an component provider was specified)
     * @return the component, or null
     */
    protected Entry getEntry(int index, boolean initialize)
    {
        Entry entry = entries.get(index);

        if ((entry != null) || (!initialize) || (componentProvider == null))
        {
            return entry;
        }

        Component component = componentProvider.getPage(index);

        return addEntry(component, index);
    }

    /**
     * Returns all entries
     * 
     * @return all entries
     */
    protected Map<Integer, Entry> getEntries()
    {
        return entries;
    }

    /**
     * Returns the index of the specified component. Just looks into the own component list, does not ask the component
     * provider for any component.
     * 
     * @param name the name of the component
     * @return the index, or Integer.MIN_VALUE if not found (as -1 may be valid)
     */
    protected int indexOf(String name)
    {
        for (Map.Entry<Integer, Entry> entry : entries.entrySet())
        {
            if (name.equals(entry.getValue().getName()))
            {
                return entry.getKey();
            }
        }

        return Integer.MIN_VALUE;
    }

    /**
     * Returns the index of the specified component. Just looks into the own component list, does not ask the component
     * provider for any component.
     * 
     * @param component the component
     * @return the index, or Integer.MIN_VALUE if not found (as -1 may be valid)
     */
    protected int indexOf(Component component)
    {
        for (Map.Entry<Integer, Entry> entry : entries.entrySet())
        {
            if (component.equals(entry.getValue().getComponent()))
            {
                return entry.getKey();
            }
        }

        return Integer.MIN_VALUE;
    }

    /**
     * Removes the component at the specified index. If you have a component provider, the component may get
     * reinitialized again, later. {@inheritDoc}
     */
    @Override
    public void remove(int index)
    {
        Entry entry = entries.remove(index);

        if (entry != null)
        {
            super.remove(entry.getComponent());
        }
    }

    /**
     * Removes the specified component. If you have a component provider, the component may get reinitialized again,
     * later. {@inheritDoc}
     */
    @Override
    public void remove(Component component)
    {
        super.remove(component);

        Iterator<Map.Entry<Integer, Entry>> iterator = entries.entrySet().iterator();

        while (iterator.hasNext())
        {
            if (iterator.next().getValue().getComponent() == component)
            {
                iterator.remove();
            }
        }
    }

    /**
     * Removes all components. If you have a component provider, the components may get reinitialized again, later.
     * {@inheritDoc}
     */
    @Override
    public void removeAll()
    {
        super.removeAll();

        entries.clear();
    }

    /**
     * Unsupported operation. {@inheritDoc}
     */
    @Override
    public void setLayout(LayoutManager mgr)
    {
        throw new UnsupportedOperationException("Layout cannot be changed");
    }

    /**
     * Returns the additional provider for components
     * 
     * @return the additional provider for components
     */
    protected JPagePaneComponentProvider getComponentProvider()
    {
        return componentProvider;
    }

    /**
     * Sets the additional provider for components. Not all components have to get added to the pane before using the
     * component. If an index is requested, and no component is defined for the index, this provider will be asked to
     * provide the component.
     * 
     * @param componentProvider the provider, may be null
     */
    protected void setComponentProvider(JPagePaneComponentProvider componentProvider)
    {
        if (!Utils.equals(this.componentProvider, componentProvider))
        {
            JPagePaneComponentProvider oldComponentProvider = this.componentProvider;

            this.componentProvider = componentProvider;

            firePropertyChange("componentProvider", oldComponentProvider, componentProvider);
        }
    }

    /**
     * Returns the number of components, that are kept as members of this pane. This is only active, if a component
     * provider is defined.
     * 
     * @return the number of components, the cache can hold
     */
    protected int getComponentCacheSize()
    {
        return componentCacheSize;
    }

    /**
     * Sets the number of components that are kept as members of this pane. This is only active, if a component provider
     * is defined.
     * 
     * @param componentCacheSize the size of the cache
     */
    protected void setComponentCacheSize(int componentCacheSize)
    {
        if (this.componentCacheSize != componentCacheSize)
        {
            int oldComponentCacheSize = this.componentCacheSize;

            this.componentCacheSize = componentCacheSize;

            firePropertyChange("componentCacheSize", oldComponentCacheSize, componentCacheSize);
        }
    }

    /**
     * Cleans the cache, if it has grown too large.
     */
    protected void cleanupComponentCache()
    {
        if (componentProvider == null)
        {
            return;
        }

        int removeCount = entries.size() - componentCacheSize;
        Iterator<Map.Entry<Integer, Entry>> iterator = entries.entrySet().iterator();
        Collection<Component> componentsToRemove = new ArrayList<Component>();

        while ((iterator.hasNext()) && (removeCount > 0))
        {
            Map.Entry<Integer, Entry> entry = iterator.next();

            iterator.remove();
            componentsToRemove.add(entry.getValue().getComponent());
            removeCount -= 1;
        }

        for (Component component : componentsToRemove)
        {
            super.remove(component);
        }
    }

    /**
     * Cleas all entries, but the selected one
     */
    protected void cleanupEntries()
    {
        Integer selectedComponentIndex = getSelectedComponentIndex();
        Iterator<Map.Entry<Integer, Entry>> iterator = getEntries().entrySet().iterator();
        Collection<Component> componentsToRemove = new ArrayList<Component>();

        while (iterator.hasNext())
        {
            Map.Entry<Integer, Entry> entry = iterator.next();

            if (selectedComponentIndex.equals(entry.getKey()))
            {
                continue;
            }

            iterator.remove();
            componentsToRemove.add(entry.getValue().getComponent());
        }

        for (Component component : componentsToRemove)
        {
            super.remove(component);
        }

        setMinimumComponentIndex(selectedComponentIndex);
        setMaximumComponentIndex(selectedComponentIndex);

        System.err.println("Selected index now: " + selectedComponentIndex);
    }

    /**
     * Adds a selection listener
     * 
     * @param selectionListener the listener
     */
    public void addSelectionListener(JPageComponentSelectionListener selectionListener)
    {
        listenerList.add(JPageComponentSelectionListener.class, selectionListener);
    }

    /**
     * Removes a selection listener
     * 
     * @param selectionListener the listener
     */
    public void removeSelectionListener(JPageComponentSelectionListener selectionListener)
    {
        listenerList.remove(JPageComponentSelectionListener.class, selectionListener);
    }

    /**
     * Fires a selection event
     * 
     * @param event the event
     */
    protected void fireComponentSelected(JPageComponentSelectionEvent event)
    {
        for (JPageComponentSelectionListener listener : listenerList
            .getListeners(JPageComponentSelectionListener.class))
        {
            listener.componentSelected(event);
        }
    }

    /**
     * Returns the minimum index for components.
     * 
     * @return the minimum index for components
     */
    protected int getMinimumComponentIndex()
    {
        return minimumComponentIndex;
    }

    /**
     * Sets the minimum index for components. The components may be initialized lazy (or late), if you specify a
     * component provider.
     * 
     * @param minimumComponentIndex the minimum component index
     */
    protected void setMinimumComponentIndex(int minimumComponentIndex)
    {
        if (this.minimumComponentIndex != minimumComponentIndex)
        {
            int oldMinimumComponentIndex = this.minimumComponentIndex;

            this.minimumComponentIndex = minimumComponentIndex;

            firePropertyChange("minimumComponentIndex", oldMinimumComponentIndex, minimumComponentIndex);
        }
    }

    /**
     * Returns the maximum index for components.
     * 
     * @return the maximum index for components
     */
    protected int getMaximumComponentIndex()
    {
        return maximumComponentIndex;
    }

    /**
     * Sets the maximum index for components. The components may be initialized lazy (or late), if you specify a
     * component provider.
     * 
     * @param maximumComponentIndex the maximum component index
     */
    protected void setMaximumComponentIndex(int maximumComponentIndex)
    {
        if (this.maximumComponentIndex != maximumComponentIndex)
        {
            int oldMaximumComponentIndex = this.maximumComponentIndex;

            this.maximumComponentIndex = maximumComponentIndex;

            firePropertyChange("maximumComponentIndex", oldMaximumComponentIndex, maximumComponentIndex);
        }
    }

    /**
     * Sets the minimum and maximum indices for components. The components may be initialized lazy (or late), if you
     * specify a component provider.
     * 
     * @param minimumComponentIndex the minimum component index
     * @param maximumComponentIndex the maximum component index
     */
    protected void setComponentIndexRange(int minimumComponentIndex, int maximumComponentIndex)
    {
        setMinimumComponentIndex(minimumComponentIndex);
        setMaximumComponentIndex(maximumComponentIndex);
    }

    /**
     * Returns true if the animation is enabled
     * 
     * @return true if the animation is enabled
     */
    public boolean isAnimationEnabled()
    {
        return animationEnabled;
    }

    /**
     * Enabled the animation
     * 
     * @param animationEnabled true to enable the animation
     */
    public void setAnimationEnabled(boolean animationEnabled)
    {
        if (this.animationEnabled != animationEnabled)
        {
            boolean oldAnimationEnabled = this.animationEnabled;

            this.animationEnabled = animationEnabled;

            firePropertyChange("animationEnabled", oldAnimationEnabled, animationEnabled);
        }
    }

    /**
     * Returns the duration for the animation in seconds
     * 
     * @return the duration for the animation in seconds
     */
    public double getAnimationDuration()
    {
        return animationDuration;
    }

    /**
     * Sets the duration of the animation in seconds
     * 
     * @param animationDuration the duration of the animation in seconds
     */
    public void setAnimationDuration(double animationDuration)
    {
        if (this.animationDuration != animationDuration)
        {
            double oldAnimationDuration = this.animationDuration;

            this.animationDuration = animationDuration;

            firePropertyChange("animationDuration", oldAnimationDuration, animationDuration);
        }
    }

    /**
     * Returns the animation
     * 
     * @return the animation
     */
    public JPageComponentAnimation getAnimation()
    {
        return animation;
    }

    /**
     * Sets the animation
     * 
     * @param animation the animation
     */
    public void setAnimation(JPageComponentAnimation animation)
    {
        if (!Utils.equals(this.animation, animation))
        {
            JPageComponentAnimation oldAnimation = this.animation;

            this.animation = animation;

            firePropertyChange("animation", oldAnimation, animation);
        }
    }

    /**
     * Returns the interpolation
     * 
     * @return the interpolation
     */
    public Interpolation getInterpolation()
    {
        return interpolation;
    }

    /**
     * Sets the interpolation
     * 
     * @param interpolation the interpolation
     */
    public void setInterpolation(Interpolation interpolation)
    {
        if (!Utils.equals(this.interpolation, interpolation))
        {
            Interpolation oldInterpolation = this.interpolation;

            this.interpolation = interpolation;

            firePropertyChange("interpolation", oldInterpolation, interpolation);
        }
    }

    /**
     * Returns the repaint early flag
     * 
     * @return the repaint early flag
     */
    public boolean isRepaintEarlyEnabled()
    {
        return repaintEarlyEnabled;
    }

    /**
     * Sets the repaint behavior. If this is enabled, all known and needed components will get rendered before the
     * animation starts. This will result in a little lag, just before the animation starts, but the animation will be
     * much smoother. If this is set to false, the animation will start immediately, but may not be that soft, because
     * the components get renderer when first needed. The default value is true. If the early repaint takes more than
     * half a second, it will be stopped.
     * 
     * @param repaintEarlyEnabled true to enable
     */
    public void setRepaintEarlyEnabled(boolean repaintEarlyEnabled)
    {
        if (this.repaintEarlyEnabled != repaintEarlyEnabled)
        {
            boolean oldRepaintEarlyEnabled = this.repaintEarlyEnabled;

            this.repaintEarlyEnabled = repaintEarlyEnabled;

            firePropertyChange("repaintEarlyEnabled", oldRepaintEarlyEnabled, repaintEarlyEnabled);
        }
    }

    /**
     * Returns the currently selected index
     * 
     * @return the currently selected index
     */
    protected Integer getSelectedComponentIndex()
    {
        return selectedComponentIndex;
    }

    /**
     * Sets the currently selected index and animates the process to display it (if the animation is generally enabled)
     * 
     * @param selectedComponentIndex the currently selected index
     */
    protected void setSelectedComponentIndex(Integer selectedComponentIndex)
    {
        setSelectedComponentIndex(selectedComponentIndex, animationEnabled);
    }

    /**
     * Sets the currently selected index
     * 
     * @param selectedComponentIndex the currently selected index
     * @param animationEnabled true to animate, false otherwise
     */
    protected void setSelectedComponentIndex(Integer selectedComponentIndex, boolean animationEnabled)
    {
        if (this.selectedComponentIndex != selectedComponentIndex)
        {
            Integer oldSelectedIndex = selectedComponentIndex;

            this.selectedComponentIndex = selectedComponentIndex;

            firePropertyChange("selectedComponentIndex", oldSelectedIndex, selectedComponentIndex);

            animate(animationEnabled);
        }
    }

    /**
     * Returns the active entry
     * 
     * @return the active entry
     */
    protected Entry getActiveEntry()
    {
        return activeEntry;
    }

    /**
     * Sets the active entry
     * 
     * @param index the index of the entry
     */
    protected void setActiveEntry(Integer index)
    {
        setActiveEntry((index != null) ? getEntry(index, true) : null);
    }

    /**
     * Sets the active entry
     * 
     * @param activeEntry the entry, that should become active
     */
    protected void setActiveEntry(Entry activeEntry)
    {
        if (Utils.equals(this.activeEntry, activeEntry))
        {
            return;
        }

        if (this.activeEntry != null)
        {
            this.activeEntry.inactivate();
        }

        this.activeEntry = activeEntry;

        if (this.activeEntry != null)
        {
            this.activeEntry.activate();
        }
    }

    /**
     * Starts the animation, if enabled. If the animation is not enabled, it triggers the new index to be visible
     */
    protected void animate(boolean animationEnabled)
    {
        setActiveEntry(selectedComponentIndex);

        if ((animationEnabled) && (animationDuration > 0) && (animation != null))
        {
            fireComponentSelected(new JPageComponentSelectionEvent(this, 0, selectedComponentIndex, true));

            if ((repaintEarlyEnabled) && (selectedComponentIndex != null))
            {
                updateImageProviders((int) displayIndex, selectedComponentIndex, 500);
            }

            animationEndMillis = (long) (System.currentTimeMillis() + (animationDuration * 1000));

            if (!animationRunning)
            {
                animationRunning = true;
                Animator.start(this, this);
            }
        }
        else
        {
            if (animationRunning)
            {
                Animator.stop(this);
            }
            else
            {
                if (selectedComponentIndex != null)
                {
                    displayIndex = selectedComponentIndex;
                }
            }

            fireComponentSelected(new JPageComponentSelectionEvent(this, 0, selectedComponentIndex, false));
        }
    }

    @Override
    public void animationStarting(Animation animation)
    {
        animationStartMillis = System.currentTimeMillis();
        animationStartIndex = displayIndex;
        animationEndIndex = selectedComponentIndex;
    }

    @Override
    public void animationFinished(Animation animation)
    {
        animationRunning = false;
        displayIndex = selectedComponentIndex;

        fireComponentSelected(new JPageComponentSelectionEvent(this, 0, selectedComponentIndex, false));
    }

    @Override
    public double animationStep(double delay, double time, double fraction)
    {
        long animationMillis = System.currentTimeMillis() - animationStartMillis;
        long animationDurationMillis = animationEndMillis - animationStartMillis;

        fraction = Utils.bound(0.0, (double) animationMillis / animationDurationMillis, 1.0);

        if (interpolation != null)
        {
            fraction = interpolation.calculate(fraction);
        }

        if (animationEndIndex != selectedComponentIndex)
        {
            animationStartIndex = selectedComponentIndex - ((selectedComponentIndex - displayIndex) / (1 - fraction));
            animationEndIndex = selectedComponentIndex;
        }

        double displayedIndex = animationStartIndex + ((selectedComponentIndex - animationStartIndex) * fraction);

        if ((displayedIndex - selectedComponentIndex) == 0)
        {
            displayIndex = selectedComponentIndex;
            displayOutgoingIndex = (int) displayedIndex;
            displayIncomingIndex = (int) displayedIndex;
            displayStill = true;
            displayForward = true;
            displayFraction = 0;

        }
        else if (displayedIndex < selectedComponentIndex)
        {
            displayIndex = displayedIndex;
            displayOutgoingIndex = (int) Math.floor(displayedIndex);
            displayIncomingIndex = (int) Math.ceil(displayedIndex);
            displayStill = false;
            displayForward = true;
            displayFraction = displayedIndex - displayOutgoingIndex;
        }
        else
        {
            displayIndex = displayedIndex;
            displayOutgoingIndex = (int) Math.ceil(displayedIndex);
            displayIncomingIndex = (int) Math.floor(displayedIndex);
            displayStill = false;
            displayForward = false;
            displayFraction = 1 - (displayedIndex - displayIncomingIndex);
        }

        repaint();

        return (fraction >= 1) ? Double.NaN : 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void paint(Graphics graphics)
    {
        if (isOpaque())
        {
            graphics.setColor(getBackground());
            graphics.fillRect(0, 0, getWidth(), getHeight());
        }

        if (animationRunning)
        {
            Graphics2D g = (Graphics2D) graphics.create();

            ImageProvider incomingImageProvider = getImageProvider(displayIncomingIndex, true);
            ImageProvider outgoingImageProvider = getImageProvider(displayOutgoingIndex, true);
            Image incomingImage = (incomingImageProvider != null) ? incomingImageProvider.getImage() : null;
            Image outgoingImage = (outgoingImageProvider != null) ? outgoingImageProvider.getImage() : null;

            if (displayStill)
            {
                if (incomingImage != null)
                {
                    g.drawImage(incomingImage, 0, 0, null);
                }
            }
            else
            {
                if (displayForward)
                {
                    animation.drawForwardAnimation(g, getWidth(), getHeight(), outgoingImage, incomingImage,
                        displayFraction);
                }
                else
                {
                    animation.drawBackwardAnimation(g, getWidth(), getHeight(), outgoingImage, incomingImage,
                        displayFraction);
                }
            }
        }
        else
        {
            super.paint(graphics);
        }
    }

}
